﻿using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;
using DeepEarth.Map.Core;
using DeepEarth.Map.Core.Extension;
using DeepEarth.Toolkit.Common;

namespace DeepEarth.Toolkit.Controls
{
	/// <summary>
	/// Having a set of "drawing processors" we can easily switch between them in runtime.
	/// </summary>
	public interface IDrawingProcessor : IDisposable
	{
		/// <summary>
		/// The event occures when user finishes drawing the shape and it is ready to be finally displayed.
		/// </summary>
		event EventHandler<GeometryEventArgs> GeometryProduced;

		/// <summary>
		/// Initializes the processor
		/// </summary>
		void Initialize();
	}

	/// <summary>
	/// A base class for all the drawing processors, does the basic initialization, etc.
	/// </summary>
	public abstract class DrawingProcessorBase : IDrawingProcessor
	{
		/// <summary>
		/// Should we keep the old cursor to be able to restore it?
		/// </summary>
		private Cursor _oldcursor;

		/// <summary>
		/// The map interaction control to work with and for.
		/// </summary>
		public MapInteractionControl MapInteractionControl { get; private set; }

		/// <summary>
		/// The reference to the map.
		/// </summary>
		protected IMap Map { get { return MapInteractionControl.Map; } }

		/// <summary>
		/// The "temp" layer, is used only when user is drawing the shape.
		/// </summary>
		protected DynamicLayer DrawLayer { get; private set; } 

		/// <summary>
		/// The event occures when user finishes drawing the shape and it is ready to be finally displayed.
		/// </summary>
		public event EventHandler<GeometryEventArgs> GeometryProduced;

		/// <summary>
		/// Creates the processor
		/// </summary>
		/// <param name="mapControl">The map interaction control to work with and for.</param>
		/// <exception cref="ArgumentNullException">When the <paramref name="mapControl"/> is <c>null</c>.</exception>
		protected DrawingProcessorBase(MapInteractionControl mapControl)
		{
			if (mapControl == null) throw new ArgumentNullException("mapControl");

			MapInteractionControl = mapControl;
			DrawLayer = new DynamicLayer();
		}

		/// <summary>
		/// Raises the <see cref="GeometryProduced"/> event.
		/// </summary>
		/// <param name="geometry">The geometry user has drawn.</param>
		protected void OnGeometryProduced(Geometry.Data.Geometry geometry)
		{
			var geometryEvent = GeometryProduced;
			if (geometryEvent != null)
				geometryEvent(this, new GeometryEventArgs(geometry));
		}

		/// <summary>
		/// Initizlizes the processor.
		/// </summary>
		public void Initialize()
		{
            _oldcursor = Map.InteractionSurface.Cursor;

            Map.InteractionSurface.Cursor = Cursors.Stylus;
			Map.InteractionSurface.IsHitTestVisible = true;
			Map.OverlayPanel.Children.Add(DrawLayer);

			InitializeProcessor();
		}

		/// <summary>
		/// Initialize the concrete processor.
		/// </summary>
		protected abstract void InitializeProcessor();

		/// <summary>
		/// Cancel and clean up the concrete processor.
		/// </summary>
		protected abstract void CancelProcessor();

		/// <summary>
		/// Cancels the processor, the non-finished shape will be cancelled and will not appear in the result.
		/// </summary>
		public void Cancel()
		{
            Map.InteractionSurface.Cursor = _oldcursor;
			//Map.OverlayPanel.ReleaseMouseCapture();
			Map.InteractionSurface.IsHitTestVisible = false;
			Map.OverlayPanel.Children.Remove(DrawLayer);
			DrawLayer.Children.Clear();

			CancelProcessor();
		}

		/// <summary>
		/// Helper, returns single click coordinates.
		/// </summary>
		/// <param name="element">The element we are looking for.</param>
		/// <returns>An observable of points when user clicks.</returns>
		protected static IObservable<Point> GetSingleClick(UIElement element)
		{
			return element.GetMouseClick()
				.Select(x => x.EventArgs.GetPosition(element))
				.Select(x => new Point(x.X, x.Y))
				.Repeat();
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Cancel();
		}
	}
}
